package cn.skyquant.quant4j.jforex.boot;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.quant.Tick;
import cn.skyquant.quant4j.api.quant.TimeBar;
import cn.skyquant.quant4j.api.quant.jforex.JForexAccount;
import cn.skyquant.quant4j.api.quant.jforex.JForexService;
import cn.skyquant.quant4j.jforex.sdk.util.EnumMatch;
import cn.skyquant.quant4j.jforex.sdk.util.QuantUtil;
import cn.skyquant.quant4j.jforex.strategy.Zeus;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.digest.AES;
import cn.skyquant.quant4j.sdk.util.log.LogUtil;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import cn.skyquant.quant4j.sdk.util.time.Period;
import com.dukascopy.api.*;
import com.dukascopy.api.system.ClientFactory;
import com.dukascopy.api.system.IClient;
import com.dukascopy.api.system.ISystemListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service("JForexManager")
public class JForexManager implements JForexService {

    @Value("${dukascopy.url}")
    private String jnlpUrl;
    @Value("${dukascopy.username}")
    private String userName ;
    @Value("${dukascopy.password}")
    private String password;
    private IClient client;
    private int lightReconnects = 3;
    private Zeus zeus = new Zeus();
    private boolean canConnect;
    private static ResultDTO error1 = ResultDTO.error("没找到交易品种");
    private static ResultDTO error2 = ResultDTO.error("没找到交易品种tick");

    @PostConstruct
    public void init(){
        try {
            client = ClientFactory.getDefaultInstance();
        } catch (Exception e) {
            LogUtil.error("jforex init error",e);
            System.exit(0);
        }
        setSystemListener();
        boolean b = start();
        if (b) LogUtil.info("jforex start success!");
    }

    @Override
    public boolean start() {
        try {
            canConnect = true;
            LogUtil.info("jforex starting...");
            tryToConnect();
            subscribeToInstruments();
            client.startStrategy(zeus);
        } catch (Exception e) {
            return false;
        }
        return client.isConnected();
    }

    @Override
    public boolean stop() {
        if (client.isConnected()) {
            canConnect = false;
            client.disconnect();
        }
        return false;
    }

    @Override
    public boolean isStart() {
        return client!=null && client.isConnected();
    }


    private void setSystemListener() {
        client.setSystemListener(new ISystemListener() {

            @Override
            public void onStart(long processId) {
                LogUtil.info("Strategy started: " + processId);
            }

            @Override
            public void onStop(long processId) {
                LogUtil.info("Strategy stopped: " + processId);
            }

            @Override
            public void onConnect() {
                LogUtil.info("jforex Connected");
                lightReconnects = 3;
            }

            @Override
            public void onDisconnect() {
                if (canConnect) {
                    tryToReconnect();
                }
            }
        });
    }

    private void tryToConnect() {
        LogUtil.info("Connecting...");
        //connect to the server using jnlp, user name and password
        try {
            client.connect(jnlpUrl, AES.decrypt(userName.trim()), AES.decrypt(password.trim()));
            //wait for it to connect
            int i = 10; //wait max ten seconds
            while (i > 0 && !client.isConnected()) {
                Thread.sleep(1000);
                i--;
            }
            if (!client.isConnected()) {
                LogUtil.error("Failed to connect Dukascopy servers");
            }
        } catch (Exception e) {
            LogUtil.error("connect Dukascopy servers error",e);
        }
    }

    private void tryToReconnect() {
        if (lightReconnects > 0) {
            client.reconnect();
            --lightReconnects;
        } else {
            do {
                try {
                    Thread.sleep(60 * 1000);
                } catch (InterruptedException e) {
                    LogUtil.error("Thread.sleep error",e);
                }
                try {
                    if (client.isConnected()) {
                        break;
                    }
                    client.connect(jnlpUrl, AES.decrypt(userName.trim()),  AES.decrypt(password.trim()));
                } catch (Exception e) {
                    LogUtil.error(e.getMessage(), e);
                }
            } while (!client.isConnected());
        }
    }

    private void subscribeToInstruments() {
        if(client.isConnected()){
            client.setSubscribedInstruments(Zeus.instruments);
        }
    }


    @Override
    public ResultDTO<JForexAccount> getAccount() {
        if(!client.isConnected() || !zeus.isStart()){
            return error;
        }
        IAccount account = zeus.getAccount();
        account.getUserName();
        account.getEquity();
        account.getBalance();
        JForexAccount jForexAccount = new JForexAccount();
        jForexAccount.balance = account.getBalance();
        jForexAccount.userName = account.getUserName();
        jForexAccount.equity = account.getEquity();
        return ResultDTO.success(jForexAccount);
    }

    @Override
    public ResultDTO<List<TimeBar>> getBar(String symbol, Period period, TradeDirection td, String from, String to) {
        if(!client.isConnected() || !zeus.isStart()){
            return error;
        }
        Instrument instrument = Instrument.valueOf(symbol);
        if(instrument==null){
            return error1;
        }
        OfferSide offerSide = EnumMatch.convertOfferSide(td);
        com.dukascopy.api.Period dukascopyperiod = EnumMatch.convertPeriod(period);
        try {
            long begin = CalendarUtils.parseGMTStandard(from).getTime();
            long end = CalendarUtils.parseGMTStandard(to).getTime();
            List<IBar> iBarList = zeus.getBars(instrument,dukascopyperiod,offerSide,Filter.WEEKENDS,begin,end);
            List<TimeBar> barList = QuantUtil.batchConvertIBarToTimeBar(iBarList);
            return ResultDTO.success(barList);
        } catch (Exception e) {
            return ResultDTO.error("jforex错误"+e.getMessage());
        }
    }

    @Override
    public ResultDTO<Tick> getLastTick(String symbol) {
        if(!client.isConnected() || !zeus.isStart()){
            return error;
        }
        Instrument instrument = Instrument.valueOf(symbol);
        if(instrument==null){
            return error1;
        }
        ITick itick = zeus.getITick(instrument);
        if(itick==null){
            return error2;
        }
        Tick tick = QuantUtil.convertITickToTick(instrument,itick);
        return ResultDTO.success(tick);
    }

    @Override
    public ResultDTO<List<Tick>> getAllTick() {
        if(!client.isConnected() || !zeus.isStart()){
            return error;
        }
        Set<Map.Entry<Instrument,ITick>> entry = zeus.getAllTick();
        List<Tick> tickList = entry
                .stream()
                .map(x->QuantUtil.convertITickToTick(x.getKey(),x.getValue()))
                .sorted(Comparator.comparing(Tick::getSymbol))
                .collect(Collectors.toList());
        return ResultDTO.success(tickList);
    }

    @Override
    public ResultDTO<Long> getTime() {
        if(!client.isConnected() || zeus.isStart()){
            return error;
        }
        return ResultDTO.success(zeus.getContextTime());
    }
}
